#include "cf_includes.h"

//=================================================
CAllocateHierarchy::CAllocateHierarchy(void)
{
}

//=================================================
CAllocateHierarchy::~CAllocateHierarchy(void)
{
}

//=================================================
HRESULT CAllocateHierarchy::AllocateName( LPCSTR name, LPSTR *pNewName )
{
  HRESULT result = S_OK;

  if( name != NULL )
  {
    UINT nameLength = (UINT)strlen(name) + 1;
    *pNewName = new CHAR[nameLength];

    if (*pNewName == NULL)
    {
      result = E_OUTOFMEMORY;
    }
    else
    {
      memcpy( *pNewName, name, nameLength*sizeof(CHAR) );
    }
  }
  else
  {
    *pNewName = NULL;
  }

  return S_OK;
}
//=================================================
HRESULT CAllocateHierarchy::GenerateSkinnedMesh( LPDIRECT3DDEVICE9 pd3dDevice, D3DXMESHCONTAINER_DERIVED *pMeshContainer )
{
  HRESULT hr = S_OK;
  D3DCAPS9 d3dCaps;
  pd3dDevice->GetDeviceCaps( &d3dCaps );

  if( pMeshContainer->pSkinInfo == NULL )
    return hr;

  bool g_bUseSoftwareVP = false;

  RELEASE_POINTER( pMeshContainer->MeshData.pMesh );
  RELEASE_POINTER( pMeshContainer->pBoneCombinationBuf );

  DWORD NumMaxFaceInfl;
  DWORD Flags = D3DXMESHOPT_VERTEXCACHE;

  LPDIRECT3DINDEXBUFFER9 pIB;
  hr = pMeshContainer->pOrigMesh->GetIndexBuffer(&pIB);
  if (FAILED(hr))
    goto e_Exit;

  hr = pMeshContainer->pSkinInfo->GetMaxFaceInfluences(pIB, pMeshContainer->pOrigMesh->GetNumFaces(), &NumMaxFaceInfl);
  pIB->Release();
  if (FAILED(hr))
    goto e_Exit;

  // 12 entry palette guarantees that any triangle (4 independent influences per vertex of a tri)
  // can be handled
  NumMaxFaceInfl = min(NumMaxFaceInfl, 12);

  if( d3dCaps.MaxVertexBlendMatrixIndex + 1 < NumMaxFaceInfl )
  {
    // HW does not support indexed vertex blending. Use SW instead
    pMeshContainer->NumPaletteEntries = min(256, pMeshContainer->pSkinInfo->GetNumBones());
    pMeshContainer->UseSoftwareVP = true;
    g_bUseSoftwareVP = true;
    Flags |= D3DXMESH_SYSTEMMEM;
  }
  else
  {
    // using hardware - determine palette size from caps and number of bones
    // If normals are present in the vertex data that needs to be blended for lighting, then 
    // the number of matrices is half the number specified by MaxVertexBlendMatrixIndex.
    pMeshContainer->NumPaletteEntries = min( ( d3dCaps.MaxVertexBlendMatrixIndex + 1 ) / 2, 
      pMeshContainer->pSkinInfo->GetNumBones() );
    pMeshContainer->UseSoftwareVP = false;
    Flags |= D3DXMESH_MANAGED;
  }

  hr = pMeshContainer->pSkinInfo->ConvertToIndexedBlendedMesh
    (
    pMeshContainer->pOrigMesh,
    Flags, 
    pMeshContainer->NumPaletteEntries, 
    pMeshContainer->pAdjacency, 
    NULL, NULL, NULL, 
    &pMeshContainer->NumInfl,
    &pMeshContainer->NumAttributeGroups, 
    &pMeshContainer->pBoneCombinationBuf, 
    &pMeshContainer->MeshData.pMesh);
  if (FAILED(hr))
    goto e_Exit;

e_Exit:
  return hr;
}


//=================================================
HRESULT CAllocateHierarchy::CreateFrame( LPCSTR name, LPD3DXFRAME *ppNewFrame )
{
  HRESULT result = S_OK;
  D3DXFRAME_DERIVED *pFrame;

  *ppNewFrame = NULL;
  pFrame = new D3DXFRAME_DERIVED;

  if (pFrame != NULL)
  {
    result = AllocateName(name, &pFrame->Name);

    if( result == S_OK )
    {
      D3DXMatrixIdentity(&pFrame->TransformationMatrix);
      D3DXMatrixIdentity(&pFrame->CombinedTransformationMatrix);

      pFrame->pMeshContainer = NULL;
      pFrame->pFrameSibling = NULL;
      pFrame->pFrameFirstChild = NULL;

      *ppNewFrame = pFrame;
      pFrame = NULL;
    }
  }
  else
  {
    result = E_OUTOFMEMORY;
  }

  return result;
}
//=================================================
HRESULT CAllocateHierarchy::CreateMeshContainer(  LPCSTR Name, CONST D3DXMESHDATA *pMeshData,
                                                CONST D3DXMATERIAL *pMaterials, CONST D3DXEFFECTINSTANCE *pEffectInstances, 
                                                DWORD NumMaterials, CONST DWORD *pAdjacency, LPD3DXSKININFO pSkinInfo, 
                                                LPD3DXMESHCONTAINER *ppNewMeshContainer) 
{
  HRESULT hr;
  D3DXMESHCONTAINER_DERIVED *pMeshContainer = NULL;
  UINT NumFaces;
  UINT iMaterial;
  UINT iBone, cBones;
  LPDIRECT3DDEVICE9 pd3dDevice = NULL;

  LPD3DXMESH pMesh = NULL;

  *ppNewMeshContainer = NULL;

  // this sample does not handle patch meshes, so fail when one is found
  if (pMeshData->Type != D3DXMESHTYPE_MESH)
  {
    hr = E_FAIL;
    goto e_Exit;
  }

  // get the pMesh interface pointer out of the mesh data structure
  pMesh = pMeshData->pMesh;

  // this sample does not FVF compatible meshes, so fail when one is found
  if (pMesh->GetFVF() == 0)
  {
    hr = E_FAIL;
    goto e_Exit;
  }

  // allocate the overloaded structure to return as a D3DXMESHCONTAINER
  pMeshContainer = new D3DXMESHCONTAINER_DERIVED;
  if (pMeshContainer == NULL)
  {
    hr = E_OUTOFMEMORY;
    goto e_Exit;
  }
  memset(pMeshContainer, 0, sizeof(D3DXMESHCONTAINER_DERIVED));

  // make sure and copy the name.  All memory as input belongs to caller, interfaces can be addref'd though
  hr = AllocateName(Name, &pMeshContainer->Name);
  if (FAILED(hr))
    goto e_Exit;        

  pMesh->GetDevice(&pd3dDevice);
  NumFaces = pMesh->GetNumFaces();

  // if no normals are in the mesh, add them
  if (!(pMesh->GetFVF() & D3DFVF_NORMAL))
  {
    pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;

    // clone the mesh to make room for the normals
    hr = pMesh->CloneMeshFVF( pMesh->GetOptions(), 
      pMesh->GetFVF() | D3DFVF_NORMAL, 
      pd3dDevice, &pMeshContainer->MeshData.pMesh );
    if (FAILED(hr))
      goto e_Exit;

    // get the new pMesh pointer back out of the mesh container to use
    // NOTE: we do not release pMesh because we do not have a reference to it yet
    pMesh = pMeshContainer->MeshData.pMesh;

    // now generate the normals for the pmesh
    D3DXComputeNormals( pMesh, NULL );
  }
  else  // if no normals, just add a reference to the mesh for the mesh container
  {
    pMeshContainer->MeshData.pMesh = pMesh;
    pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;

    pMesh->AddRef();
  }

  // allocate memory to contain the material information.  This sample uses
  //   the D3D9 materials and texture names instead of the EffectInstance style materials
  pMeshContainer->NumMaterials = max(1, NumMaterials);
  pMeshContainer->pMaterials = new D3DXMATERIAL[pMeshContainer->NumMaterials];
  pMeshContainer->ppTextures = new LPDIRECT3DTEXTURE9[pMeshContainer->NumMaterials];
  pMeshContainer->pAdjacency = new DWORD[NumFaces*3];
  if ((pMeshContainer->pAdjacency == NULL) || (pMeshContainer->pMaterials == NULL))
  {
    hr = E_OUTOFMEMORY;
    goto e_Exit;
  }

  memcpy(pMeshContainer->pAdjacency, pAdjacency, sizeof(DWORD) * NumFaces*3);
  memset(pMeshContainer->ppTextures, 0, sizeof(LPDIRECT3DTEXTURE9) * pMeshContainer->NumMaterials);

  // if materials provided, copy them
  if (NumMaterials > 0)            
  {
    memcpy(pMeshContainer->pMaterials, pMaterials, sizeof(D3DXMATERIAL) * NumMaterials);

    for (iMaterial = 0; iMaterial < NumMaterials; iMaterial++)
    {
      if (pMeshContainer->pMaterials[iMaterial].pTextureFilename != NULL)
      {
        WCHAR wszBuf[MAX_PATH];
        WCHAR fullPath[MAX_PATH];
        MultiByteToWideChar( CP_ACP, 0, pMeshContainer->pMaterials[iMaterial].pTextureFilename, -1, wszBuf, MAX_PATH );
        GetFullPathName( wszBuf, MAX_PATH, fullPath, NULL );
        if( FAILED( D3DXCreateTextureFromFile( pd3dDevice, fullPath,
          &pMeshContainer->ppTextures[iMaterial] ) ) )
          pMeshContainer->ppTextures[iMaterial] = NULL;

        // don't remember a pointer into the dynamic memory, just forget the name after loading
        pMeshContainer->pMaterials[iMaterial].pTextureFilename = NULL;
      }
    }
  }
  else // if no materials provided, use a default one
  {
    pMeshContainer->pMaterials[0].pTextureFilename = NULL;
    memset(&pMeshContainer->pMaterials[0].MatD3D, 0, sizeof(D3DMATERIAL9));
    pMeshContainer->pMaterials[0].MatD3D.Diffuse.r = 0.5f;
    pMeshContainer->pMaterials[0].MatD3D.Diffuse.g = 0.5f;
    pMeshContainer->pMaterials[0].MatD3D.Diffuse.b = 0.5f;
    pMeshContainer->pMaterials[0].MatD3D.Specular = pMeshContainer->pMaterials[0].MatD3D.Diffuse;
  }

  // if there is skinning information, save off the required data and then setup for HW skinning
  if (pSkinInfo != NULL)
  {
    // first save off the SkinInfo and original mesh data
    pMeshContainer->pSkinInfo = pSkinInfo;
    pSkinInfo->AddRef();

    pMeshContainer->pOrigMesh = pMesh;
    pMesh->AddRef();

    // Will need an array of offset matrices to move the vertices from the figure space to the bone's space
    cBones = pSkinInfo->GetNumBones();
    pMeshContainer->pBoneOffsetMatrices = new D3DXMATRIX[cBones];
    if (pMeshContainer->pBoneOffsetMatrices == NULL)
    {
      hr = E_OUTOFMEMORY;
      goto e_Exit;
    }

    // get each of the bone offset matrices so that we don't need to get them later
    for (iBone = 0; iBone < cBones; iBone++)
    {
      pMeshContainer->pBoneOffsetMatrices[iBone] = *(pMeshContainer->pSkinInfo->GetBoneOffsetMatrix(iBone));
    }

    // GenerateSkinnedMesh will take the general skinning information and transform it to a HW friendly version
    hr = GenerateSkinnedMesh( pd3dDevice, pMeshContainer );
    if (FAILED(hr))
      goto e_Exit;
  }

  *ppNewMeshContainer = pMeshContainer;
  pMeshContainer = NULL;

e_Exit:
  RELEASE_POINTER(pd3dDevice);

  // call Destroy function to properly clean up the memory allocated 
  if (pMeshContainer != NULL)
  {
    DestroyMeshContainer(pMeshContainer);
  }

  return hr;
}
//=================================================
HRESULT CAllocateHierarchy::DestroyFrame(LPD3DXFRAME pFrameToFree) 
{
  DELETE_ARRAY( pFrameToFree->Name );
  DELETE_POINTER( pFrameToFree );

  return S_OK; 
}
//=================================================
HRESULT CAllocateHierarchy::DestroyMeshContainer(LPD3DXMESHCONTAINER pMeshContainerBase)
{
  UINT iMaterial;
  D3DXMESHCONTAINER_DERIVED *pMeshContainer = (D3DXMESHCONTAINER_DERIVED*)pMeshContainerBase;

  DELETE_ARRAY( pMeshContainer->Name );
  DELETE_ARRAY( pMeshContainer->pAdjacency );
  DELETE_ARRAY( pMeshContainer->pMaterials );
  DELETE_ARRAY( pMeshContainer->pBoneOffsetMatrices );

  // release all the allocated textures
  if (pMeshContainer->ppTextures != NULL)
  {
    for (iMaterial = 0; iMaterial < pMeshContainer->NumMaterials; iMaterial++)
    {
      RELEASE_POINTER( pMeshContainer->ppTextures[iMaterial] );
    }
  }

  DELETE_ARRAY( pMeshContainer->ppTextures );
  DELETE_ARRAY( pMeshContainer->ppBoneMatrixPtrs );
  RELEASE_POINTER( pMeshContainer->pBoneCombinationBuf );
  RELEASE_POINTER( pMeshContainer->MeshData.pMesh );
  RELEASE_POINTER( pMeshContainer->pSkinInfo );
  RELEASE_POINTER( pMeshContainer->pOrigMesh );
  DELETE_POINTER( pMeshContainer );
  return S_OK;
}

//=================================================
CCF_AnimMesh::CCF_AnimMesh(void)
{
}

//=================================================
CCF_AnimMesh::~CCF_AnimMesh(void)
{
}
//=================================================
HRESULT CCF_AnimMesh::SetupBoneMatrixPointers( LPD3DXFRAME pFrame )
{
  HRESULT hr;

  if( pFrame != NULL )
  {
    if (pFrame->pMeshContainer != NULL)
    {
      hr = SetupBoneMatrixPointersOnMesh(pFrame->pMeshContainer);
      if (FAILED(hr))
        return hr;
    }

    if (pFrame->pFrameSibling != NULL)
    {
      hr = SetupBoneMatrixPointers(pFrame->pFrameSibling);
      if (FAILED(hr))
        return hr;
    }

    if (pFrame->pFrameFirstChild != NULL)
    {
      hr = SetupBoneMatrixPointers(pFrame->pFrameFirstChild);
      if (FAILED(hr))
        return hr;
    }
  }

  return S_OK;
}

//=================================================
HRESULT CCF_AnimMesh::SetupBoneMatrixPointersOnMesh( LPD3DXMESHCONTAINER pMeshContainerBase )
{
  UINT iBone, cBones;
  D3DXFRAME_DERIVED *pFrame;

  D3DXMESHCONTAINER_DERIVED *pMeshContainer = (D3DXMESHCONTAINER_DERIVED*)pMeshContainerBase;

  // if there is a skinmesh, then setup the bone matrices
  if (pMeshContainer->pSkinInfo != NULL)
  {
    cBones = pMeshContainer->pSkinInfo->GetNumBones();

    pMeshContainer->ppBoneMatrixPtrs = new D3DXMATRIX*[cBones];
    if (pMeshContainer->ppBoneMatrixPtrs == NULL)
      return E_OUTOFMEMORY;

    for (iBone = 0; iBone < cBones; iBone++)
    {
      pFrame = (D3DXFRAME_DERIVED*)D3DXFrameFind( m_rootFrame, pMeshContainer->pSkinInfo->GetBoneName(iBone) );
      if (pFrame == NULL)
        return E_FAIL;

      pMeshContainer->ppBoneMatrixPtrs[iBone] = &pFrame->CombinedTransformationMatrix;
    }
  }

  return S_OK;
}

//=================================================
void CCF_AnimMesh::UpdateFrameMatrices( LPD3DXFRAME pFrameBase, LPD3DXMATRIX pParentMatrix )
{
  D3DXFRAME_DERIVED *pFrame = (D3DXFRAME_DERIVED*)pFrameBase;

  if (pParentMatrix != NULL)
    D3DXMatrixMultiply(&pFrame->CombinedTransformationMatrix, &pFrame->TransformationMatrix, pParentMatrix);
  else
    pFrame->CombinedTransformationMatrix = pFrame->TransformationMatrix;

  if (pFrame->pFrameSibling != NULL)
  {
    UpdateFrameMatrices(pFrame->pFrameSibling, pParentMatrix);
  }

  if (pFrame->pFrameFirstChild != NULL)
  {
    UpdateFrameMatrices(pFrame->pFrameFirstChild, &pFrame->CombinedTransformationMatrix);
  }
}


//=================================================
void CCF_AnimMesh::UpdateSkinningMethod( LPD3DXFRAME pFrameBase )
{
  D3DXFRAME_DERIVED *pFrame = (D3DXFRAME_DERIVED*)pFrameBase;
  D3DXMESHCONTAINER_DERIVED *pMeshContainer;

  pMeshContainer = (D3DXMESHCONTAINER_DERIVED *)pFrame->pMeshContainer;

  while( pMeshContainer != NULL )
  {
    m_allocHierarchy->GenerateSkinnedMesh( m_device, pMeshContainer );

    pMeshContainer = (D3DXMESHCONTAINER_DERIVED *)pMeshContainer->pNextMeshContainer;
  }

  if (pFrame->pFrameSibling != NULL)
  {
    UpdateSkinningMethod(pFrame->pFrameSibling);
  }

  if (pFrame->pFrameFirstChild != NULL)
  {
    UpdateSkinningMethod(pFrame->pFrameFirstChild);
  }
}


//=================================================
void CCF_AnimMesh::DrawMeshContainer( IDirect3DDevice9 *pd3dDevice, LPD3DXMESHCONTAINER pMeshContainerBase, LPD3DXFRAME pFrameBase )
{
  HRESULT hr;
  D3DXMESHCONTAINER_DERIVED *pMeshContainer = (D3DXMESHCONTAINER_DERIVED*)pMeshContainerBase;
  D3DXFRAME_DERIVED *pFrame = (D3DXFRAME_DERIVED*)pFrameBase;
  UINT iMaterial;
  UINT NumBlend;
  UINT iAttrib;
  DWORD AttribIdPrev;
  LPD3DXBONECOMBINATION pBoneComb;

  UINT iMatrixIndex;
  UINT iPaletteEntry;
  D3DXMATRIXA16 matTemp;
  D3DCAPS9 d3dCaps;
  pd3dDevice->GetDeviceCaps( &d3dCaps );

  pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );
  // first check for skinning
  if (pMeshContainer->pSkinInfo != NULL)
  {
    // if hw doesn't support indexed vertex processing, switch to software vertex processing
    if (pMeshContainer->UseSoftwareVP)
    {
      // If hw or pure hw vertex processing is forced, we can't render the
      // mesh, so just exit out.  Typical applications should create
      // a device with appropriate vertex processing capability for this
      // skinning method.

      pd3dDevice->SetSoftwareVertexProcessing(TRUE);
    }

    // set the number of vertex blend indices to be blended
    if (pMeshContainer->NumInfl == 1)
    {
      pd3dDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_0WEIGHTS);
    }
    else
    {
      pd3dDevice->SetRenderState(D3DRS_VERTEXBLEND, pMeshContainer->NumInfl - 1);
    }

    if (pMeshContainer->NumInfl)
      pd3dDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, TRUE);

    // for each attribute group in the mesh, calculate the set of matrices in the palette and then draw the mesh subset
    pBoneComb = reinterpret_cast<LPD3DXBONECOMBINATION>(pMeshContainer->pBoneCombinationBuf->GetBufferPointer());
    for (iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++)
    {
      // first calculate all the world matrices
      for (iPaletteEntry = 0; iPaletteEntry < pMeshContainer->NumPaletteEntries; ++iPaletteEntry)
      {
        iMatrixIndex = pBoneComb[iAttrib].BoneId[iPaletteEntry];
        if (iMatrixIndex != UINT_MAX)
        {
          D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] );
          pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( iPaletteEntry ), &matTemp );
        }
      }

      // setup the material of the mesh subset - REMEMBER to use the original pre-skinning attribute id to get the correct material id
      pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D );
      pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pBoneComb[iAttrib].AttribId] );

      // finally draw the subset with the current world matrix palette and material state
      pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib );
    }

    // reset blending state
    pd3dDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, FALSE);
    pd3dDevice->SetRenderState(D3DRS_VERTEXBLEND, 0);

    // remember to reset back to hw vertex processing if software was required
    if (pMeshContainer->UseSoftwareVP)
    {
      pd3dDevice->SetSoftwareVertexProcessing(FALSE);
    }
  }
  else  // standard mesh, just draw it after setting material properties
  {
    pd3dDevice->SetTransform(D3DTS_WORLD, &pFrame->CombinedTransformationMatrix);

    for (iMaterial = 0; iMaterial < pMeshContainer->NumMaterials; iMaterial++)
    {
      pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[iMaterial].MatD3D );
      pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[iMaterial] );
      pMeshContainer->MeshData.pMesh->DrawSubset(iMaterial);
    }
  }
  pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW );
}

//=================================================
void CCF_AnimMesh::DrawFrame( IDirect3DDevice9 *pd3dDevice, LPD3DXFRAME pFrame )
{
  LPD3DXMESHCONTAINER pMeshContainer;

  pMeshContainer = pFrame->pMeshContainer;
  while (pMeshContainer != NULL)
  {
    DrawMeshContainer( pd3dDevice, pMeshContainer, pFrame );

    pMeshContainer = pMeshContainer->pNextMeshContainer;
  }

  if (pFrame->pFrameSibling != NULL)
  {
    DrawFrame( pd3dDevice, pFrame->pFrameSibling);
  }

  if (pFrame->pFrameFirstChild != NULL)
  {
    DrawFrame( pd3dDevice, pFrame->pFrameFirstChild );
  }
}

//=================================================
void CCF_AnimMesh::Initialize(LPDIRECT3DDEVICE9 device, LPWSTR filename)
{
  m_device = device;
  m_allocHierarchy = new CAllocateHierarchy();

  WCHAR fullpath[ MAX_PATH ];
  GetFullPathName( filename, MAX_PATH, fullpath, NULL );

  HRESULT res = D3DXLoadMeshHierarchyFromX( filename, D3DXMESH_MANAGED, device,
    m_allocHierarchy, NULL, &m_rootFrame, &m_animController );

  SetupBoneMatrixPointers(m_rootFrame);
}
//=================================================
void CCF_AnimMesh::Draw()
{
  DrawFrame( m_device, m_rootFrame );
}
//=================================================
void CCF_AnimMesh::UpdateAnimation( double delta )
{
  D3DXMATRIXA16 matr;
  D3DXMatrixIdentity( &matr );

  m_animController->AdvanceTime( delta, NULL );

  UpdateFrameMatrices( this->m_rootFrame, &matr );
}
//=================================================
void CCF_AnimMesh::Update()
{
  D3DXMATRIXA16 matr;
  D3DXMatrixIdentity( &matr );
  UpdateFrameMatrices( this->m_rootFrame, &matr );
}

